package com.maochd.cloud.excel.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.maochd.cloud.common.core.constant.CommonConstant;
import com.maochd.cloud.common.core.domain.UserInfo;
import com.maochd.cloud.excel.annotation.ExportMethod;
import com.maochd.cloud.excel.annotation.ExportSheet;
import com.maochd.cloud.excel.annotation.ImportMethod;
import com.maochd.cloud.excel.dto.ExportCommonSheetDTO;
import com.maochd.cloud.excel.dto.ImportDto;
import com.maochd.cloud.excel.enums.ExportEnum;
import com.maochd.cloud.excel.enums.ImportEnum;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.MutableTriple;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Spring 工具类
 *
 * @author maochd
 */
@Slf4j
@Component(value = "springUtils")
public class SpringUtils implements ApplicationContextAware {


    /**
     * Spring 上下文
     */
    private static ApplicationContext context = null;

    /**
     * 缓存@Service注入实例的所有方法
     */
    private static Map<Class<?>, Method[]> implCacheMap = new LinkedHashMap<>();

    /**
     * 导出获取数据源方法缓存
     */
    public static Map<String, MutablePair<? extends Class<?>, Method>> exportCacheMap = new LinkedHashMap<>();

    /**
     * 导出获取数据源方法缓存
     */
    public static Map<String, MutablePair<? extends Class<?>, Method>> importCacheMap = new LinkedHashMap<>();


    /**
     * 通过class获取bean
     *
     * @param clazz 类型
     */
    public static <T> T getBean(Class<T> clazz) {
        return context.getBean(clazz);
    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        SpringUtils.context = context;
        implCacheMap = context.getBeansWithAnnotation(Service.class).
                values().stream().map(Object::getClass).
                collect(Collectors.toMap(Function.identity(), ReflectUtil::getMethods));
    }

    /**
     * 初始化导出获取数据源方法
     */
    @PostConstruct
    private void initExportOrImportMethod() {
        // 缓存导出方法
        exportCacheMap = implCacheMap.entrySet().stream().
                flatMap(en -> Arrays.stream(en.getValue()).
                        filter(m -> m.isAnnotationPresent(ExportMethod.class)).
                        map(m -> MutablePair.of(en.getKey(), m))).
                collect(Collectors.toMap(mp ->
                        mp.right.getAnnotation(ExportMethod.class).type().getName(), Function.identity()));
        // 缓存导入方法
        importCacheMap = implCacheMap.entrySet().stream().flatMap(fm -> Arrays.stream(fm.getValue())
                        .filter(f -> f.isAnnotationPresent(ImportMethod.class))
                        .map(m -> MutablePair.of(fm.getKey(), m)))
                .collect(Collectors.toMap(k -> k.getRight().getAnnotation(ImportMethod.class)
                        .type().getName(), Function.identity()));
        // 清空Impl方法缓存
        implCacheMap.clear();
    }

    /**
     * 根据名称获取导出方法
     *
     * @param name 方法中文名
     * @return Method
     */
    public static MutablePair<? extends Class<?>, Method> exportMethod(String name) {
        return exportCacheMap.getOrDefault(name, null);
    }

    /**
     * 根据名称获取导入方法
     *
     * @param name 方法中文名
     * @return Method
     */
    public static MutablePair<? extends Class<?>, Method> importMethod(String name) {
        return importCacheMap.getOrDefault(name, null);
    }

    /**
     * 获取子父类所有字段
     *
     * @param clazz 对象
     * @return List
     */
    public static List<Field> listAllField(Class<?> clazz) {
        List<Field> fieldList = Lists.newArrayList();
        while (clazz != null) {
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        return fieldList;
    }

    /**
     * 执行导出获取数据源方法
     *
     * @param type 导出类型
     * @param po   条件
     */
    @SneakyThrows
    public static List<MutableTriple<? extends Class<?>, String, ? extends List<?>>> invokeExport(ExportEnum type, JSONObject po) {
        MutablePair<? extends Class<?>, Method> mp = exportMethod(type.getName());
        if (Objects.isNull(mp)) {
            throw new RuntimeException("处理数据方法获取失败");
        }
        Method method = mp.right;
        Object impl = SpringUtils.getBean(mp.left);
        Class<?>[] parameterTypes = method.getParameterTypes();
        Object[] objects = new Object[]{};
        // 执行方法查询数据
        if (CollUtil.isNotEmpty(Arrays.asList(parameterTypes))) {
            Object arg = JSON.parseObject(po.toJSONString(), method.getParameterTypes()[0]);
            objects = new Object[]{arg};
        }
        // 获得返回值
        List<?> returnList = (List<?>) method.invoke(impl, objects);
        return returnList.stream().map(m -> {
                    List<?> list = (List<?>) m;
                    Class<?> exportClass = CollUtil.isEmpty(list) ? ExportCommonSheetDTO.class : list.get(0).getClass();
                    // 获取导出类型的sheet注解
                    ExportSheet exportSheet = exportClass.getAnnotation(ExportSheet.class);
                    return MutableTriple.of(exportClass, exportSheet, list);
                }).sorted(Comparator.comparing(c -> c.getMiddle().sheetNo()))
                .map(m -> MutableTriple.of(m.getLeft(), m.getMiddle().sheetName(), m.getRight()))
                .collect(Collectors.toList());
    }

    /**
     * 解析异常
     *
     * @param e 异常
     * @return toString
     */
    public static String exception(Exception e) {
        if (e == null) {
            return null;
        }
        // 获取异常堆栈
        String element = Arrays.stream(e.getStackTrace()).
                filter(st -> StringUtils.contains(st.getClassName(), CommonConstant.BASE_PACKAGE)).
                map(String::valueOf).findFirst().orElse(StrUtil.EMPTY);
        return e.fillInStackTrace() + StrUtil.LF + element;
    }


    /**
     * 执行导入方法
     *
     * @param type        导入类型
     * @param inputStream 输入流
     * @param importDto   导入参数
     * @return pair
     */
    @SneakyThrows
    public static MutableTriple<Class<?>, List<?>, List<?>> invokeImportDeal(ImportEnum type, InputStream inputStream, ImportDto importDto) {
        MutablePair<? extends Class<?>, Method> mp = importMethod(type.getName());
        if (Objects.isNull(mp)) {
            throw new RuntimeException("（导入）数据处理方法获取失败");
        }
        Method method = mp.getRight();
        Object impl = SpringUtils.getBean(mp.getLeft());
        ImportMethod importMethod = method.getAnnotation(ImportMethod.class);
        // 读取文件内容
        List<?> list = ExcelUtils.readExcel(inputStream, importMethod.clazz());
        if (CollUtil.isNotEmpty(list)) {
            Object data = list.get(list.size() - 1);
            String[] fieldName = getFieldName(data);
            // 取出每个字段
            List<Object> fieldValueList = Arrays.stream(fieldName).map(m -> getFieldValue(data, m)).collect(Collectors.toList());
            long count = fieldValueList.stream().filter(Objects::isNull).count();
            if (count == fieldName.length) {
                list = list.subList(0, list.size() - 1);
            }
        }
        importDto.setList(list);
        // 调用方法处理业务数据
        List<?> failList = (List<?>) method.invoke(impl, new Object[]{importDto});
        // 获取返回值类型泛型
        return MutableTriple.of(importMethod.clazz(), failList, list);
    }

    /**
     * 获取字段名称数组
     *
     * @param o 数据对象
     * @return 字段名称数组
     */
    public static String[] getFieldName(Object o) {
        Field[] fields = o.getClass().getDeclaredFields();
        String[] fieldNames = new String[fields.length];
        for (int i = 0; i < fields.length; i++) {
            if (fields[i].isAnnotationPresent(ExcelProperty.class)) {
                fieldNames[i] = fields[i].getName();
            }
        }
        return fieldNames;
    }

    /**
     * 获取字段值
     *
     * @param o    数据对象
     * @param name 字段名称
     * @return 字段值
     */
    public static Object getFieldValue(Object o, String name) {
        try {
            Field[] fields = o.getClass().getDeclaredFields();
            Object object = null;
            if (StrUtil.isBlank(name)) {
                return null;
            }
            for (Field field : fields) {
                // 可以获取到私有属性
                field.setAccessible(true);
                if (field.getName().equalsIgnoreCase(name)) {
                    object = field.get(o);
                    break;
                }
            }
            return object;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 封装导出方法返回值
     *
     * @param list 返回值列表数组
     * @return list
     */
    public static List<List<?>> exportConvert(List<?>... list) {
        return Arrays.stream(list).map(p -> CollUtil.isEmpty(p) ? new ArrayList<>() : p).collect(Collectors.toList());
    }

    /**
     * 当列表为空时生成单行空数据列表
     *
     * @param clazz 列表对象
     * @return list
     */
    public static <T> List<T> generateListWithEmpty(Class<T> clazz) throws InstantiationException, IllegalAccessException {
        return new ArrayList<T>() {{
            add(clazz.newInstance());
        }};
    }

}
